package ifi.ro.apvs.gui;

import static java.awt.geom.AffineTransform.getRotateInstance;
import static java.awt.geom.AffineTransform.getTranslateInstance;

import java.awt.BasicStroke;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.AffineTransform;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random;

import javax.swing.JOptionPane;
import javax.swing.JTextArea;

class GraphPad extends Canvas {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private boolean addEnable = false;
	private boolean dragEnable = true;
	private boolean drawLine = false;
	private boolean isOriented = false;
	private int x = -1, y = -1, traceX = -1, traceY = -1;
	private int selectedPoint = -1;
	private Cursor curCursor;
	private ArrayList<GPoint> pointCollection;
	private Rectangle2D bounds;
	private Image doubleBuffer;
	private Random rand = new Random(); 

	// private boolean step = false;
	public GraphPad() {
		setBackground(Color.white);
		addMouseListener(new MyMouseListener(this));
		addMouseMotionListener(new MyMouseMotionListener(this));
		this.pointCollection = new ArrayList<GPoint>();

	}

	public void update(Graphics g) {
		Dimension size = getSize();
		doubleBuffer = createImage(size.width, size.height);
		if (doubleBuffer != null) {
			// paint to double buffer
			Graphics g2 = doubleBuffer.getGraphics();
			paint(g2);
			g2.dispose();
			// copy double buffer to screen
			g.drawImage(doubleBuffer, 0, 0, null);
		} else {
			// couldn't create double buffer, just paint to screen
			paint(g);
		}
	}

	public void paint(Graphics g) {
		Graphics2D g2D = (Graphics2D) g;
		g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON); 
		GPoint p;

		/* Tracking line */
		if (drawLine && x != -1 && y != -1) {
			g2D.setColor(Color.blue);
			g2D.drawLine(x, y, traceX, traceY);
		}

		/* Tracking selected point */
		if (selectedPoint != -1 && bounds != null) {
			g2D.setColor(Color.DARK_GRAY);
			double x, y, w, h;
			x = bounds.getX();
			y = bounds.getY();
			w = bounds.getWidth();
			h = bounds.getHeight();
			g2D.drawRect((int) x, (int) y, 3, 3);
			g2D.drawRect((int) (x + w - 3), (int) (y), 3, 3);
			g2D.drawRect((int) (x), (int) (y + h - 3), 3, 3);
			g2D.drawRect((int) (x + w - 3), (int) (y + h - 3), 3, 3);
		}

		if (this.pointCollection != null) {
			GPoint target;
			/* Draw the lines */
			g2D.setStroke(new BasicStroke(2f, BasicStroke.CAP_ROUND,
					BasicStroke.JOIN_ROUND));
			for (int i = 0; i < this.pointCollection.size(); i++) {
				p = pointCollection.get(i);
				if (p.lineTo != null) {
					for (int j = 0; j < p.lineTo.size(); j++) {
						g2D.setColor(p.lineTo.getColor(j));
						target = p.lineTo.get(j);
						int x1, y1, x2, y2;
						x1 = (int) (p.getX() + p.getRadius());
						y1 = (int) (p.getY() + p.getRadius());
						x2 = (int) (target.getX() + target.getRadius());
						y2 = (int) (target.getY() + target.getRadius());
						if (isOriented) {
							AffineTransform bkup = g2D.getTransform();
							double dx = (x2 - x1) / 2, dy = (y2 - y1) / 2;
							int idx = target.lineTo.indexOf(p);
							if (idx >= 0 && target.lineTo.isDraw(idx)) {
								dy = dy - 40;
								QuadCurve2D q = new QuadCurve2D.Float();
								q.setCurve(x1, y1, (x1 + x2) / 2,
										(y1 + y2) / 2 - 80, x2, y2);
								g2D.draw(q);
							} else {
								g2D.drawLine(x1, y1, x2, y2);
								p.lineTo.setDraw(j, true);
							}
							double angle = Math.atan2(dy, dx);
							int len = (int) Math.sqrt(dx * dx + dy * dy);
							AffineTransform at = getTranslateInstance(x1, y1);
							at.concatenate(getRotateInstance(angle));
							g2D.setTransform(at);
							g2D.fillPolygon(new int[] { len, len - 8, len - 8,
									len }, new int[] { 0, -8, 8, 0 }, 4);
							g2D.setTransform(bkup);

						} else {
							g2D.drawLine(x1, y1, x2, y2);
							p.lineTo.setDraw(j, true);
						}
						/* draw arrow */
						if (isOriented) {

						}
					}
				}
			}
			g2D.setStroke(new BasicStroke(2f));
			/* Draw the points */
			for (int i = 0; i < this.pointCollection.size(); i++) {
				p = pointCollection.get(i);
				g2D.setColor(p.fillColor);
				g2D.fill(p);
				g2D.setColor(Color.black);
				g2D.drawString(p.getLabel(),
						(int) (p.getX() + p.getRadius()) - 5, (int) (p.getY()
								+ p.getRadius() + 5));
				g2D.setColor(p.borderColor);
				g2D.draw(this.pointCollection.get(i));
			}

		}

		if (curCursor != null)
			setCursor(curCursor);
	}

	public void enableAdd(boolean b) {
		this.drawLine = false;
		this.dragEnable = true;
		this.addEnable = b;
	}

	public void drawLine(boolean b) {
		this.drawLine = true;
		if (b)
			this.isOriented = true;
		else
			this.isOriented = false;
		if (this.drawLine) {
			this.dragEnable = false;
			this.addEnable = false;
		} else
			this.dragEnable = true;
	}

	public void deletePoint() {
		this.drawLine = false;
		this.dragEnable = true;
		this.addEnable = false;
		if (this._delPoint(this.selectedPoint, this.pointCollection))
			this.selectedPoint = -1;
	}

	private boolean _delPoint(int location, ArrayList<GPoint> G) {
		if (location != -1) {
			GPoint p = G.get(location);
			for (int i = 0; i < G.size(); i++) {
				GPoint q = G.get(i);
				if (q.lineTo.contains(p)) {
					int idx = q.lineTo.indexOf(p);
					q.lineTo.remove(idx);
				}

			}
			G.remove(location);
			return true;
		}
		return false;
	}

	public void clearAll() {
		this.pointCollection = new ArrayList<GPoint>();
		this.selectedPoint = -1;
		this.dragEnable = true;
		this.addEnable = false;
		this.drawLine = false;
		repaint();
	}

	public void select() {
		this.dragEnable = true;
		this.addEnable = false;
		this.drawLine = false;
	}

	@SuppressWarnings("unchecked")
	public void componentConnected(JTextArea txt) {
		selectedPoint = -1;
		this.addEnable = false;
		this.drawLine = false;
		this.dragEnable = true;
		txt.setText("");

		ArrayList<GPoint> G = this.myClone(pointCollection);
		ArrayList<GPoint> Gbackup = (ArrayList<GPoint>) G.clone();

		if (this.isOriented) {
			G = convertOriented_NonOriented(pointCollection);
		}

		/* Alors qu'il y a des points, chercher la composante connexe */
		ArrayList<GPoint> C = new ArrayList<GPoint>();
		ArrayList<GPoint> Q = new ArrayList<GPoint>();

		int idx;
		while (G.size() > 0) {

			GPoint v = G.get(0);
			C.add(v);
			Q.add(v);

			txt.setText(txt.getText() + "* calcule composante\n");
			txt.setText(txt.getText() + "  " + v.getLabel() + "->Q\n");
			txt.setText(txt.getText() + "  " + v.getLabel() + "->C\n");
			Color c = new Color(rand.nextFloat(), rand.nextFloat(), rand.nextFloat());
			//c.darker();
			idx = Gbackup.indexOf(v);
			pointCollection.get(idx).quickColor(c);
			repaint();

			while (Q.size() > 0) {

				GPoint p = Q.get(0);
				txt.setText(txt.getText() + "  Depuis " + p.getLabel() + ":\n");
				for (int i = 0; i < p.lineTo.size(); i++) {
					if (!C.contains(p.lineTo.get(i))) {
						C.add(p.lineTo.get(i));
						Q.add(p.lineTo.get(i));

						txt.setText(txt.getText() + "    "
								+ p.lineTo.get(i).getLabel() + "->C\n");
						txt.setText(txt.getText() + "    "
								+ p.lineTo.get(i).getLabel() + "->Q\n");
						idx = Gbackup.indexOf(p.lineTo.get(i));
						System.out.println(idx);
						pointCollection.get(idx).quickColor(c);
						sleep();
						repaint();
					}
				}
				Q.remove(0);
				G.remove(G.indexOf(p));

			}
			C = new ArrayList<GPoint>();
			Q = new ArrayList<GPoint>();
		}
	}

	public void componentStronglyConnected(JTextArea txt) {
		ArrayList<GPoint> G = this.myClone(pointCollection);
		ArrayList<GPoint> G1 = this.myClone(pointCollection);

		ArrayList<GPoint> S = new ArrayList<GPoint>();
		ArrayList<GPoint> S1 = new ArrayList<GPoint>();
		ArrayList<GPoint> S2 = new ArrayList<GPoint>();

		for (int k = 0; k < G.size(); k++) {
			if (!S.contains(G.get(k))) {
				GPoint v = G.get(k);
				S1.add(v);
				while (S1.size() > 0) {
					GPoint w = S1.get(S1.size() - 1);
					for (int i = 0; i < w.lineTo.size(); i++) {
						if (!S.contains(w.lineTo.get(i))
								&& !S1.contains(w.lineTo.get(i))) {
							S1.add(w.lineTo.get(i));
							break;
						}
					}
					if (S1.get(S1.size() - 1).equals(w)) {
						S.add(w);
						S1.remove(S1.size() - 1);
					}

				}
			}
		}

		/* Renverser le graph */

		for (int i = 0; i < G1.size(); i++) {
			GPoint p = G1.get(i);
			int offset = 0;
			for (int j = 0; j < p.lineTo.size(); j++) {
				GPoint q = p.lineTo.get(j);
				if (!q.lineTo.contains(p)) {
					G.get(i).lineTo.get(j - offset).lineTo.add(G.get(i));
					G.get(i).lineTo.remove(j - offset);
					offset++;
				}
			}
		}

		while (S.size() > 0) {
			Color c = new Color(rand.nextFloat(), rand.nextFloat(), rand.nextFloat());
			//c.darker();

			GPoint x = S.get(S.size() - 1);
			S1.add(x);
			while (S1.size() > 0) {
				GPoint y = S1.get(S1.size() - 1);
				for (int i = 0; i < y.lineTo.size(); i++) {
					if (!S2.contains(y.lineTo.get(i))
							&& !S1.contains(y.lineTo.get(i))) {
						S1.add(y.lineTo.get(i));
						break;
					}
				}
				
				if (S1.get(S1.size() - 1).equals(y)) {
					S2.add(y);
					S1.remove(S1.size() - 1);
				}
			}

			// S2 composante connexe
			for (int i = S2.size() - 1; i >= 0; i--) {
				int idx = G1.indexOf(S2.get(i));
				pointCollection.get(idx).fillColor = c;
				for (int j = 0; j < G1.get(idx).lineTo.size(); j++) {
					if (S2.contains(G1.get(idx).lineTo.get(j)))
						pointCollection.get(idx).lineTo.setColor(j, c);
				}

				if (S.indexOf(S2.get(i)) != -1) {
					S.remove(S.indexOf(S2.get(i)));
					this._delPoint(G.indexOf(S2.get(i)), G);
				}

				repaint();
				txt.setText(txt.getText() + S2.get(i).getLabel() + " ");
				sleep();
			}
			txt.setText(txt.getText() + "\n");
			S2 = new ArrayList<GPoint>();

		}
		/* for test */

	}

	public void setMatrix(ArrayList<ArrayList<Integer>> matrix) {
		this.clearAll();
		this.isOriented = false;
		/* initialiser les noeufs */
		for (int i = 0; i < matrix.size(); i++) {
			pointCollection.add(new GPoint(Math.random() * 700,
					Math.random() * 600, "v" + String.valueOf(i + 1)));
		}

		/* ajouter les arcs entre les sommets */
		for (int i = 0; i < matrix.size(); i++)
			for (int j = 0; j < matrix.get(i).size(); j++) {
				if (!this.isOriented)
					if (Math.abs(matrix.get(i).get(j) - matrix.get(j).get(i)) == 1)
						this.isOriented = true;

				if (matrix.get(i).get(j) == 1) {
					pointCollection.get(i).lineTo.add(
							this.pointCollection.get(j), Color.DARK_GRAY);
				}
			}
		repaint();
	}

	private int getTargetPoint(double x, double y) {
		for (int i = 0; i < pointCollection.size(); i++) {
			if (pointCollection.get(i).contains(x, y)
					|| pointCollection.get(i).contains(x - 10, y - 10)
					|| pointCollection.get(i).contains(x + 10, y + 10)
					|| pointCollection.get(i).contains(x - 10, y + 10)
					|| pointCollection.get(i).contains(x + 10, y - 10))
				return i;
		}
		return -1;
	}

	private void sleep() {
		long time = new Date().getTime();
		long i = time + 1500;
		while (time < i)
			time = new Date().getTime();
	}

	private ArrayList<GPoint> convertOriented_NonOriented(ArrayList<GPoint> G) {
		ArrayList<GPoint> alReturn = new ArrayList<GPoint>();
		alReturn = this.myClone(G);
		for (int i = 0; i < G.size(); i++) {
			GPoint p = alReturn.get(i);
			for (int j = 0; j < p.lineTo.size(); j++) {
				GPoint q = p.lineTo.get(j);
				if (!q.lineTo.contains(p))
					alReturn.get(i).lineTo.get(j).lineTo.add(p);
			}
		}
		return alReturn;
	}

	private ArrayList<GPoint> myClone(ArrayList<GPoint> ar) {
		ArrayList<GPoint> alRet = new ArrayList<GPoint>();
		for (int i = 0; i < ar.size(); i++) {
			GPoint p = new GPoint(ar.get(i).x, ar.get(i).y, ar.get(i)
					.getLabel());
			alRet.add(p);
		}
		for (int i = 0; i < ar.size(); i++) {
			GPoint p = ar.get(i);
			for (int j = 0; j < p.lineTo.size(); j++) {
				GPoint q = p.lineTo.get(j);
				int idx = ar.indexOf(q);
				alRet.get(i).lineTo.add(alRet.get(idx));
			}
		}

		return alRet;
	}

	/* the inner classe */
	class MyMouseListener extends MouseAdapter {
		GraphPad canvas;

		public MyMouseListener(GraphPad gp) {
			canvas = gp;
		}

		public void mousePressed(MouseEvent e) {
			int ellipse = getTargetPoint(e.getX(), e.getY());
			if (selectedPoint != -1) {
				selectedPoint = -1;
				canvas.repaint();
			}
			if (ellipse > -1) {
				selectedPoint = ellipse;
				bounds = pointCollection.get(ellipse).getBounds2D();
				x = traceX = e.getX();
				y = traceY = e.getY();
				canvas.repaint();
			}

		}

		public void mouseReleased(MouseEvent e) {
			int ellipse = getTargetPoint(e.getX(), e.getY());
			if (ellipse > -1) {
				if (!drawLine) {
					selectedPoint = ellipse;
					bounds = pointCollection.get(ellipse).getBounds2D();
				} else if (selectedPoint != -1 && ellipse != selectedPoint) {

					pointCollection.get(selectedPoint).lineTo.add(
							pointCollection.get(ellipse), Color.DARK_GRAY);
					if (!isOriented)
						pointCollection.get(ellipse).lineTo.add(
								pointCollection.get(selectedPoint),
								Color.DARK_GRAY);
					selectedPoint = -1;
					canvas.repaint();
				}

			}
			x = y = -1;
			traceX = traceY = -1;
		}

		public void mouseClicked(MouseEvent e) {
			int ellipse = getTargetPoint(e.getX(), e.getY());
			if (ellipse > -1) {
				selectedPoint = ellipse;
				bounds = pointCollection.get(ellipse).getBounds2D();
			} else {
				if (selectedPoint != -1) {
					selectedPoint = -1;
				}
				if (addEnable) {
					GPoint p;
					String s = JOptionPane
							.showInputDialog("Please enter label for the node");
					if (s != null) {
						p = new GPoint(e.getX(), e.getY(), s);

					} else
						p = new GPoint(e.getX(), e.getY(), "v"
								+ String.valueOf(pointCollection.size() + 1));

					pointCollection.add(p);
					selectedPoint = -1;

				}
				canvas.repaint();
			}
		}
	}

	class MyMouseMotionListener extends MouseMotionAdapter {
		private GraphPad canvas;

		public MyMouseMotionListener(GraphPad canvas) {
			this.canvas = canvas;
		}

		public void mouseDragged(MouseEvent e) {
			int ellipse = getTargetPoint(e.getX(), e.getY());
			if (ellipse > -1 && dragEnable) {
				selectedPoint = ellipse;
				bounds = pointCollection.get(ellipse).getBounds2D();
				GPoint p = pointCollection.get(ellipse);
				int x1 = e.getX();
				int y1 = e.getY();
				p.x = pointCollection.get(selectedPoint).getX() + x1 - x;
				p.y = pointCollection.get(selectedPoint).getY() + y1 - y;
				pointCollection.set(ellipse, p);
				x = x1;
				y = y1;
			} else {
				traceX = e.getX();
				traceY = e.getY();
			}
			canvas.repaint();

		}

		public void mouseMoved(MouseEvent e) {
			int ellipse = getTargetPoint(e.getX(), e.getY());
			if (ellipse > -1)
				curCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
			else
				curCursor = Cursor.getDefaultCursor();
			canvas.repaint();
		}
	}
}